package com.qiangzi.workflow.engine.bpmn.base;

import java.util.Collection;
import java.util.Map;

import org.springframework.util.Assert;

import com.qiangzi.workflow.engine.bpmn.base.Edge;
import com.qiangzi.workflow.engine.bpmn.base.Node;
import com.qiangzi.workflow.engine.bpmn.event.StartEvent;
import com.qiangzi.workflow.engine.bpmn.expression.ConditionExpression;
import com.qiangzi.workflow.engine.bpmn.flow.SequenceFlow;
import com.qiangzi.workflow.engine.bpmn.gateway.ExclusiveGateway;
import com.qiangzi.workflow.engine.bpmn.gateway.ParallelGateway;

public class ProcessDefinition {

	// 以下几个变量,在解析完成后,事实不可变
	private StartEvent startEvent;

	private final Map<String, Node> nodeMap;

	private final Map<String, Edge> edgeMap;

	public ProcessDefinition(Map<String, Node> nodeMap, Map<String, Edge> edgeMap) throws Exception {

		Assert.isTrue(null != nodeMap, "not valid node map");
		Assert.isTrue(null != edgeMap, "not valid edge map");
		this.nodeMap = nodeMap;
		this.edgeMap = edgeMap;

		buildConnection();

	}

	/**
	 * 点和边进行关联->形成1个完整的拓扑图 ,否则孤立的没有意义
	 * 
	 * @throws Exception
	 * 
	 */
	private void buildConnection() throws Exception {

		// 这里我们来构造拓扑图,在构造的过程中我们来做有效性验证
		// 我们以边来作为主视角来串联起来点和边
		Collection<Edge> edges = edgeMap.values();

		for (Edge edge : edges) {

			String srcNodeId = edge.getSourceRef();
			String tgtNodeId = edge.getTargetRef();

			Node srcNode = nodeMap.get(srcNodeId);
			Node tgtNode = nodeMap.get(tgtNodeId);

			Assert.isTrue(null != srcNode, "find not node , not valid src node id -> " + srcNodeId);
			Assert.isTrue(null != tgtNode, "find not node , not valid target node id -> " + tgtNodeId);

			// 现在我们已经知道这个边和这2个点都有关系

			// 完善源节点的信息点
			srcNode.getOutgoing().add(edge);
			srcNode.setConnected(true);

			// 完善目标节点的信息点
			tgtNode.getIncoming().add(edge);
			tgtNode.setConnected(true);

			// 完善edge的信息点
			edge.setSrcNode(srcNode);
			edge.setTargetNode(tgtNode);
			edge.setConnected(true);

			if (edge instanceof SequenceFlow) {

				SequenceFlow flow = (SequenceFlow) edge;
				ConditionExpression conditionExpression = flow.getConditionExpression();

				// 如果点是排它网关,则边的条件表达式应该不为空
				if (srcNode instanceof ExclusiveGateway) {

					if (null == conditionExpression) {
						throw new Exception("the all edges of ExclusiveGateway node " + srcNode.getId()
								+ " should has condition expression");
					}

					String expression = conditionExpression.getExpression();
					if (null == expression || expression.trim().length() <= 0) {
						throw new Exception("the all edges of ExclusiveGateway node " + srcNode.getId()
								+ " should has condition expression");
					}

				}
				// 如果点是并行网关,则条件表达式必须为空
				else if (srcNode instanceof ParallelGateway) {
					if (null != conditionExpression) {
						throw new Exception("the all edges of ExclusiveGateway node " + srcNode.getId()
								+ " should no condition expression,delete it and retry");
					}
				}
			}

		}

		// 上面已经保证了每个边都用到了,所以只要判断是不是每个点都被使用了
		// 顺便发现startEvent,而且必须唯一
		Collection<Node> nodes = nodeMap.values();
		for (Node node : nodes) {
			Assert.isTrue(node.isConnected(), "node not used , check your xml , id is ->" + node.getId());
			if (node instanceof StartEvent) {
				Assert.isTrue(null == startEvent, "startEvent XML element must be only 1");
				startEvent = (StartEvent) node;
			}
		}
		// 必须有且只有1个startEvent
		Assert.isTrue(null != startEvent, "startEvent must be defined in xml");

	}

	public ProcessInstance deploy() throws Exception {

		// 深度部署点-nodeMap事实不变对象-只读
		Collection<Node> nodes = nodeMap.values();
		for (Node node : nodes) {
			node.deploy();
		}

		// 深度部署边-edgeMap事实不变对象-只读
		Collection<Edge> edges = edgeMap.values();
		for (Edge edge : edges) {
			edge.deploy();
		}

		// 执行是以点为主,点才是要执行的任务,边只是作为顺序+判断等用途
		ProcessInstance instance = new ProcessInstance();
		// 确保一个合格的流程开始是至少有1个起点,不然执行啥
		Assert.isTrue(null != startEvent, "start event not exist,check your xml definition");
		instance.addCandidate(startEvent);
		instance.setDeployThread(Thread.currentThread());
		return instance;
	}

}